package com.zsc.oj.zojcodesendbox.judge;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.resource.ResourceUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.dfa.FoundWord;
import cn.hutool.dfa.WordTree;
import com.zsc.oj.zojcodesendbox.utils.ProcessUtils;
import com.zsc.oj.zojcodesendbox.utils.model.ExecuteMessage;
import lombok.extern.slf4j.Slf4j;

import java.io.File;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.UUID;

/**
 * java代码沙箱模板方法的实现
 */
@Slf4j
public abstract class JavaCodeSendBoxTemplate implements CodeSendBox {

    public static final String GLOBAL_CODE_DIR_NAME = "tempCode";

    public static final String FILE_NAME = "Main.java";

    @Override
    public ExecuteCodeResponse ExecuteCode(ExecuteCodeRequest executeCodeRequest) {
        String code = executeCodeRequest.getCode();

        //1、把用户代码保存为文件
        File userCodeFile = saveUserCode(code);

        //2、编译代码
        compileCode(userCodeFile);

        //3、运行代码
        //获取输入用例
        List<String> inputList = executeCodeRequest.getInputList();
        List<ExecuteMessage> executeMessageList = runCode(inputList, userCodeFile);

        //4、收集整理输出结果
        ExecuteCodeResponse executeCodeResponse = collectionResponse(executeMessageList);

        //5、清理文件
        boolean b = deleteFile(userCodeFile);
        if(b){
            log.info("删除成功");
        }else{
            log.error("删除失败");
        }

        return executeCodeResponse;
    }

    /**
     * 保存用户代码
     * @param UserCode：用户代码
     * @return
     */
    public File saveUserCode(String UserCode){
        //定位到项目的根目录
        String RootUrl = System.getProperty("user.dir");
        //在根目录下创建一个tempCode文件夹用来存放用户代码
        String globalPathName = RootUrl + File.separator + GLOBAL_CODE_DIR_NAME;

        //如果tempCode文件夹不存在，则创建一个
        if(!FileUtil.exist(globalPathName)){
            FileUtil.mkdir(globalPathName);
        }

        //通过UUID把用户的代码隔离存放，因为每个用户提交的代码都是main函数
        String codeParentPathName = globalPathName + File.separator + UUID.randomUUID();
        //将用户代码文件写入tempCode文件夹中
        File file = FileUtil.writeString(UserCode, codeParentPathName + File.separator + FILE_NAME, StandardCharsets.UTF_8);
        return file;
    }

    /**
     * 编译
     * @param userCodefile
     * @return
     */
    public ExecuteMessage compileCode(File userCodefile){
        //定义命令行语句
        String compileCode = String.format("javac -encoding utf-8 %s",userCodefile.getAbsolutePath());
        try {
            //执行命令行语句
            Process process = Runtime.getRuntime().exec(compileCode);
            //调用自定义工具类查看编译信息
            ExecuteMessage executeMessage = ProcessUtils.getProcessMessage(process,"编译");
            System.out.println(executeMessage);
            return executeMessage;
        } catch (Exception e) {
            //todo 修改为自定义异常
            throw new RuntimeException("编译失败");
        }
    }

    /**
     * 运行文件
     * @param inputList：输入用例
     * @param userCodeFile：用户代码文件
     * @return
     */
    public List<ExecuteMessage> runCode(List<String> inputList,File userCodeFile){
        String path = userCodeFile.getParentFile().getAbsolutePath();
        //收集所以的运行信息
        List<ExecuteMessage> executeMessageList = new ArrayList<>();
        for(String input : inputList){
            //定义命令行语句
            String runCmd = String.format("java -Dfile.encoding=UTF-8 -cp %s Main %s",path,input);
            try {
                //执行命令行语句
                Process process = Runtime.getRuntime().exec(runCmd);
                //调用自定义工具类查看编译信息
                ExecuteMessage executeMessage = ProcessUtils.getProcessMessage(process, "运行");
                System.out.println(executeMessage);
                executeMessageList.add(executeMessage);
            } catch (Exception e) {
                throw new RuntimeException("运行失败");
            }
        }
        return executeMessageList;
    }

    /**
     * 收集整理输出结果
     * @param executeMessageList
     * @return
     */
    public ExecuteCodeResponse collectionResponse(List<ExecuteMessage> executeMessageList){
        ExecuteCodeResponse executeCodeResponse = new ExecuteCodeResponse();
        List<String> outputList = new ArrayList<>();
        //选用最大值，便于判断是否超时
        long maxTime = 0;
        for (ExecuteMessage executeMessage : executeMessageList) {
            //如果运行时发生错误
            String errorMessage = executeMessage.getErrorMessage();
            if(StrUtil.isNotBlank(errorMessage)){
                executeCodeResponse.setMessage(errorMessage);
                executeCodeResponse.setStatus(3);
                break;
            }
            //将运行的正确信息设置到outputList
            outputList.add(executeMessage.getSuccessMessage());
            //超时时间
            Long time = executeMessage.getTime();
            if(time != null){
                maxTime = Math.max(maxTime,time);
            }
        }
        if(outputList.size() == executeMessageList.size()){
            executeCodeResponse.setStatus(1);
        }

        executeCodeResponse.setOutputList(outputList);

        JudgeInfo judgeInfo = new JudgeInfo();
        judgeInfo.setTime(maxTime);
        executeCodeResponse.setJudgeInfo(judgeInfo);

        return executeCodeResponse;
    }

    /**
     * 删除文件
     * @param userCodeFile
     * @return
     */
    public boolean deleteFile(File userCodeFile){
        if(userCodeFile.getParentFile() != null){
            boolean del = FileUtil.del(userCodeFile);
            if(del){
                System.out.println("删除成功");
                return true;
            }else{
                System.out.println("删除失败");
                return false;
            }
        }
        return true;
    }

    /**
     * 获取错误码
     * @param e
     * @return
     */
    private ExecuteCodeResponse getErrorResponse(Throwable e){
        ExecuteCodeResponse executeCodeResponse = new ExecuteCodeResponse();
        executeCodeResponse.setOutputList(new ArrayList<>());
        executeCodeResponse.setMessage(e.getMessage());
        //2 表示代码沙箱错误
        executeCodeResponse.setStatus(2);
        executeCodeResponse.setJudgeInfo(new JudgeInfo());
        return executeCodeResponse;
    }
}
